#-*- coding: utf-8 -*-

import observers 
import strategies
import stockmodel
import helpers
import math

# TODO: 
#   * dopisać ewaluator biorący pod uwagę ryzyko poniesione podczas inwestycji
#   * generować dane do rysowania
#   * powywalać settery

class Evaluator(object): # {{{
    """ 
    Dekorator do funkcji celu, oceniającej osobnika.

    Dodaje funkcję funkcję eval_all, która w przyszłości może np. liczyć wartości równolegle.

    Dodatkowo posiada listę obserwatorów, których informuje o jakości rozwiązań.
    (chyba jedynym obserwatorem powinna być klasa ewoluująca, dodająca od siebie
    informacje o ewolucji, jak np. numer pokolenia)



    Sygnatura funkcji celu jest następująca:

    fitness = function(chromosome, *args)

    A definiuje się ją tak:
    @Evaluator
    def function(chromosome, args):
        pass


    (w tej klasie można rozszerzyć IoC, pozwalając podmienić strategię)
    """

    # początkowa lista jest obiektem klasy, dzięki czemu można dodać obserwatorów tylko 
    # raz za pomocą Evaluator.observers = [...]
    observers = []

    def __init__(self, function):
        self.function  = function
        self.observers = Evaluator.observers

    def __call__(self, chromosome, *args):
        fitness, data = self.function(self, chromosome, *args)
        self.notifyObservers(data)
        return fitness

    def evalAll(self, chromosomes, *args):
        fitnesses = []
        for chromosome in chromosomes:
            fitness, data = self.function(self, chromosome, *args)
            fitnesses.append((chromosome, fitness))
            self.notifyObservers(data)
        return fitnesses

    def attachObserver(self, observer):
        if not observer in self.observers:
            self.observers.append(observer)

    def notifyObservers(self, *data):
        if data[0] != None:
            for observer in self.observers:
                observer(*data)

# }}}

class StockEvaluator(Evaluator): # {{{ 
    initialMoney        = 0.0    # kwota do inwestycji *w złotych*
    initialShares       = 0.0
    strategies          = []     # lista strategii inwestycyjnych (klasa Stategy)
    ticker              = ""     # symbol spółki (dostępne w data/*.mst)
    buyRatio            = 0.5    # przy sygnale kupna kupujemy floor(money * buyRatio / kursAkcji) akcji
    sellRatio           = 0.5    # przy sygnale sprzedaży sprzedajemy sellRatio * ilośćAkcji
    stockModel          = None
    firstSimulationDay  = None   # pierwszy dzień symulacji. Dla None oznacza 0
    lastSimulationDay   = None   # ostatni dzień symulacji. gdy jest za duży, symulacja kończy się z dniem ostatniego notowania

    def __init__(self, function):
        super(StockEvaluator, self).__init__(function)
    
    def __call__(self, chromosome, *args):
        self.stockModel.reset()
        return super(StockEvaluator, self).__call__(chromosome, *args)

    def setInitialShares(self, initialShares):
        assert initialShares >= 0
        self.initialShares = initialShares

    def setInitialAsset(self, initialMoney):
        assert initialMoney > 0
        self.initialMoney = initialMoney

    def setStrategies(self, strategies):
        self.strategies = strategies

    def setBuyRatio(self, sellRatio):
        assert 0 <= sellRatio <= 1
        self.sellRatio = sellRatio

    def setSellRatio(self, buyRatio):
        assert 0 <= buyRatio <= 1
        self.buyRatio = buyRatio

    def setStockSymbol(self, ticker, lastSimulationDay = None, firstSimulationDay = None):
        self.ticker = ticker
        self.stockModel = stockmodel.StockModel(ticker, lastSimulationDay, firstSimulationDay)

# }}}

@Evaluator
def onemax(params, chromosome):
    return (sum(chromosome), None)

@Evaluator
def deceptiveonemax(params, chromosome):
    fitness = sum(chromosome) 
    length = len(chromosome)

    if fitness == length:
        fitness = length+1

    return (fitness, str(chromosome)+": "+str(fitness))

@StockEvaluator
def evalAssetsReturn(params, chromosome): # {{{
    """
    Każdy niezerowy gen chromosomu oznacza aktywną strategię inwestycyjną.
    Ewaluator dla każdego dnia zbiera sygnały od aktywnych strategii inwestycyjnych
    i wykonuje zlecenie zgodne z decyzją większości 

    Zlecenia kupna i sprzedaży składane są bez limitu.
    """
    
    activeStrategies = [_f for _f in [gene_strategy[1] if gene_strategy[0] else None for gene_strategy in zip(chromosome, params.strategies)] if _f]

    shares     = params.initialShares
    sharePrice = 0
    money      = params.initialMoney

    while params.stockModel.advanceDay():

        buyCount   = 0
        sellCount  = 0
        sharePrice = params.stockModel.getSharePrice()

        for strategy in activeStrategies:
            order = strategy(params.stockModel)
            #order = strategy(params.stockModel)['order']
            if   order == True:
                buyCount  += 1
            elif order == False:
                sellCount += 1
    
        if sellCount < buyCount:
            # kupujemy
            toBuyShares = math.floor(money * params.buyRatio / sharePrice)
            
            shares     += toBuyShares
            money      -= toBuyShares * sharePrice

        elif sellCount > buyCount:
            # sprzedajemy
            toSellShares = math.floor(params.sellRatio * shares)
            shares      -= toSellShares
            money       += toSellShares * sharePrice

        s = "%d,%d" % (params.stockModel.currentDayIndex, money + sharePrice * shares)
        params.notifyObservers(params.stockModel.currentDayIndex, money + sharePrice * shares)
        assert money >= 0

        #fitness = money + sharePrice * shares
        #print "assets: %d (in money: %d, shares: %d)" %  (fitness, money, shares)

    s = "%d,%d" % (params.stockModel.currentDayIndex, money + sharePrice * shares)
    params.money  = money
    params.shares = shares
    params.sharePrice = sharePrice
    #params.notifyObservers((money, shares))
    fitness = money + sharePrice * shares

    #return fitness, "%s : assets: %6d (in money: %6d, shares: %6d)" %  (str(chromosome), fitness, money, shares)
    return fitness, None 
# }}}


if __name__ == '__main__':

    evaluator = evalAssetsReturn
    evaluator.setStockSymbol('cerabud')
    evaluator.setInitialAsset(100)
    evaluator.setStrategies(strategies.strategies)
    evaluator.attachObserver(observers.stdoutObserver)

    chromosome = [1] * len(evaluator.strategies)
    #chromosome = helpers.randomIndividual(helpers.initialProbabilityVector(len(evaluator.strategies)))
    fitnesses = []

    evaluator(chromosome)
    #for i in xrange(10):
    #    fitness = evaluator(chromosome)
    #    fitnesses.append(fitness)

    print("średnia z 10 symulacji: ", sum(fitnesses)/len(fitnesses))


# vim: fdm=marker
